package Managers;

import java.util.Calendar;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;

/*
 * Since the CategoryManager Implements the singleton pattern, all methods which
 * can be are static, because there should only ever been one category manager.
 */
public class CalendarManager implements ICMObservable
{
	//Sotres days
	private static Hashtable<Integer, Day[][]> YEARS;
	//single pattern
	private static CalendarManager instance;
	//the current day thats being pointed too
	private Calendar now;
	//implementing the observer pattern
	private List<ICMObserver> observers;
	
	//constructor
	private CalendarManager()
	{
		//Initialisation
		YEARS = new Hashtable<Integer, Day[][]>();
		setNow(getToday());
	}
	
	//Implement the singleton pattern =)
	public static CalendarManager getInstance()
	{
		if(instance == null)
		{
			instance = new CalendarManager();
		}
		
		return instance;
			
	}
	
	//method for caluclating is a year is a leap year
	public static boolean isLeap(int year)
	{
		if(year % 400 == 0)
			return true;
		else if(year % 100 == 0)
			return false;
		else if(year % 4 == 0)
			return true;
		else
			return false;
	}
	
	public static int getYearNumber()
	{
		return getInstance().getNow().get(Calendar.YEAR);
	}
	//get the size of a particular month
	public static int getMonthSize(int y, int m)
	{
		return getYear(y)[m].length;
	}
	//get the size of a particular month
	public  static int getMonthSize(Calendar c)
	{
		return getMonthSize(c.get(Calendar.YEAR), c.get(Calendar.MONTH));
	}
	//get the size of a particular month
	public  static int getMonthSize()
	{
		return getMonthSize(getInstance().getNow());
	}
	
	//get the name of a month
	public static String getMonthName(int m)
	{
		String[] months = new String[]{
				"January",
				"Februry",
				"March",
				"April",
				"May",
				"June",
				"July",
				"August",
				"September",
				"October",
				"November",
				"December"};
		
		return months[m];
	}
	
	//create a year this method automatically adds the new year to the 
	private static Day[][] createYear(int yearNum)
	{
		 Day[][] year = new Day[12][];
		 year[0] = new Day[31];//Jan
		 year[1] = new Day[isLeap(yearNum) ? 29 : 28];//Feb//leap year
		 year[2] = new Day[31];//mar
		 year[3] = new Day[30];//apr
		 year[4] = new Day[31];//may
		 year[5] = new Day[30];//jun
		 year[6] = new Day[31];//jul
		 year[7] = new Day[31];//aug
		 year[8] = new Day[30];//sep
		 year[9] = new Day[31];//oct
		 year[10] = new Day[30];//nov
		 year[11] = new Day[31];//dec
		 
		 for(int i = 0; i < year.length; i++)
		 {
			 for(int m = 0; m < year[i].length; m++)
			 {
				 year[i][m] = new Day(yearNum, i, m+1);
			 }
		 }
		 
		YEARS.put(yearNum, year);
		
		return year;
	}
	
	//gets  a specific day
	public static Day getDay(Calendar c)
	{
		int y = c.get(Calendar.YEAR);
		int m = c.get(Calendar.MONTH);
		int d = c.get(Calendar.DAY_OF_MONTH)-1;
		
		return getDay(y,m,d);
	}
	//get s a specific day
	public static Day getDay(int y, int m, int d)
	{
		return getYear(y)[m][d];
	}
	
	//gets the day that now is pointing too
	public static Day getDay()
	{
		return getDay(getInstance().getNow());
	}
	
	//gets a weeks worth of days starting from the day passed in
	public static Day[] getWeek(Calendar start_of_week)
	{
		 int y = start_of_week.get(Calendar.YEAR);
		 int m = start_of_week.get(Calendar.MONTH);
		 int d = start_of_week.get(Calendar.DAY_OF_MONTH)-1;
		 
		return getWeek(y, m, d);
	}
	
	//gets a week based on the year, month and day
	public static Day[] getWeek(int y, int m, int d)
	{		 
		 Day[] week = new Day[7];
		 Day[][] year = getYear(y);
		 Day[][] yearafter = getYear(y+1);
		 for(int i = 0; i < 7; i++)
		 {
			 if(d+i < year[m].length)
			 {
				 week[i] = year[m][d+i];
			 }
			 else
			 {
				 if((m+1) == 12)
				 {
					 week[i] = yearafter[0][(d+i) - year[m].length ];
				 }
				 else
				 {
					 week[i] = year[m+1][(d+i) - year[m].length ];
				 }
			 }
		 }
		 
		 return week;
	}
	
	//gets a week based on what now is pointing too.
	public static Day[] getWeek()
	{
		return getWeek(getInstance().getNow());
	}
	
	//gets a day based on what day it is in real life.
	public static Calendar getToday()
	{
		Calendar c = Calendar.getInstance();
		c.set(Calendar.DAY_OF_MONTH, c.get(Calendar.DAY_OF_MONTH));
		return c;
	}
	
	//gets a years worth of days
	public static Day[][] getYear(int yearNum)
	{
		//get a year
		Day[][] y = YEARS.get(yearNum);
		//if y is null then create it
		if(y == null)
		{
			return createYear(yearNum);//return it
		}
		else
		{
			return y; //return what we retrieved
		}
	}
	
	
	//gets a year based on what the calendar year is
	public static Day[][] getYear(Calendar c)
	{
		//get year number
		int year = c.get(Calendar.YEAR);
		
		return getYear(year);
	}
	
	//get the year based on what now points too
	public static Day[][] getYear()
	{
		return getYear(getInstance().getNow());
	}
	//get a month worth of days
	public static Day[] getMonth(Calendar start_of_month)
	{
		//get yera and month
		 int y = start_of_month.get(Calendar.YEAR);
		 int m = start_of_month.get(Calendar.MONTH);
		 //return the month
		 return getMonth(y,m);
	}
	
	//gets the month based on what year and month it is
	public static Day[] getMonth(int y, int m)
	{
		return getYear(y)[m];
	}
	//get the month which  now points too
	public static Day[] getMonth()
	{
		return getMonth(getInstance().getNow());
	}

	//add an appointment to the day which it starts
	public static void AddAppointment(Day d, Appointment a)
	{
		if(a.getRepeat() == null)
		{
			d.addAppointment(a);
		}
		else
		{
			AddAppointment(a);
		}
		
		getInstance().notifyObservers();
	}	
	public static void AddAppointment(Appointment a)
	{
		if(a.getRepeat() != null)
		{
			Appointment current = new Appointment(a);
			
			Repeat r = current.getRepeat();
			
			if(r.getRepeats().equalsIgnoreCase("weekly"))
			{
				while(current.getStart().getTime().before(r.getRepeatUntil()))
				{
					getDay(current.getStart()).addAppointment(current);
					current = new Appointment(current);
					current.getFinish().add(Calendar.WEEK_OF_YEAR, 1);
					current.getStart().add(Calendar.WEEK_OF_YEAR, 1);
				}
			}
			else if(r.getRepeats().equalsIgnoreCase("Daily"))
			{
				while(a.getStart().getTime().before(r.getRepeatUntil()))
				{
					getDay(current.getStart()).addAppointment(current);
					current = new Appointment(current);
					current.getFinish().add(Calendar.DAY_OF_YEAR, 1);
					current.getStart().add(Calendar.DAY_OF_YEAR, 1);
				}	
			}
			else if(r.getRepeats().equalsIgnoreCase("Monthly"))
			{
				while(a.getStart().getTime().before(r.getRepeatUntil()))
				{
					getDay(current.getStart()).addAppointment(current);
					current = new Appointment(current);
					current.getFinish().add(Calendar.MONTH, 1);
					current.getStart().add(Calendar.MONTH, 1);
				}	
			}
			else if(r.getRepeats().equalsIgnoreCase("Yearly"))
			{
				while(a.getStart().getTime().before(r.getRepeatUntil()))
				{
					getDay(current.getStart()).addAppointment(current);
					current = new Appointment(current);
					current.getFinish().add(Calendar.YEAR, 1);
					current.getStart().add(Calendar.YEAR, 1);
				}
			}
			else
			{
				System.err.println("FAIL - " + r.getRepeatEvery());
			}
			
		}
		else
		{
			getDay(a.getStart()).addAppointment(a);
			
		}
		getInstance().notifyObservers();
	}
	
	public static void DeleteAppointment(Day d, Appointment a)
	{
		d.deleteAppointment(a);
		getInstance().notifyObservers();
	}	
	
	public static void DeleteAppointment(Appointment a)
	{
		DeleteAppointment(getDay(a.getStart()), a);
	}

	//these next two methods are used to implement the observer pattern
	@Override
	public void addObserver(ICMObserver io)
	{
		if(observers == null)
			observers = new LinkedList<ICMObserver>();
		
		observers.add(io);
	}
	
	public static void addObservers(ICMObserver io)
	{
		getInstance().addObserver(io);
	}

	
	//call update on anything observing the CalendarManager
	@Override
	public void notifyObservers()
	{
		if(observers == null)
			return;
		else
			for(ICMObserver iob : observers)
				iob.Update();
		
	}
	
	
	@Override
	public void notifyObservers(Category[] cats)
	{
		if(observers == null)
			return;
		else
			for(ICMObserver iob : observers)
				iob.Update(cats);
	}

	//getters and setter for the now attribute
	public void setNow(Calendar n) {
		now = n;
		notifyObservers();
	}

	public Calendar getNow() {
		return now;
	}

	
	//these methods are used for changing what day points too. and then notifys anything observing of the changes. self explanatory.
	public static void AdvanceDay()
	{
		getInstance().getNow().add(Calendar.DATE, 1);
		getInstance().notifyObservers();
	}
	
	public static void DeclineDay()
	{
		getInstance().getNow().add(Calendar.DATE, -1);
		getInstance().notifyObservers();
	}	
	
	public static void AdvanceMonth()
	{
		getInstance().getNow().add(Calendar.MONTH, 1);
		getInstance().notifyObservers();
	}
	
	public static void DeclineMonth()
	{
		getInstance().getNow().add(Calendar.MONTH, -1);
		getInstance().notifyObservers();
	}
	
	public static void AdvanceWeek()
	{
		getInstance().getNow().add(Calendar.WEEK_OF_YEAR, 1);
		getInstance().notifyObservers();
	}
	
	public static void DeclineWeek()
	{
		getInstance().getNow().add(Calendar.WEEK_OF_YEAR, -1);
		getInstance().notifyObservers();
	}
	
	public static void AdvanceYear()
	{
		getInstance().getNow().add(Calendar.YEAR, 1);
		getInstance().notifyObservers();
	}
	
	public static void DeclineYear()
	{
		getInstance().getNow().add(Calendar.YEAR, -1);
		getInstance().notifyObservers();
	}

	
		
}
